package com.jctrip.hotel.management.service;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jctrip.hotel.common.enums.RouteTypeEnum;
import com.jctrip.hotel.domain.dao.*;
import com.jctrip.hotel.domain.entity.*;
import com.jctrip.hotel.management.model.param.GetFinanceInfoParam;
import com.jctrip.hotel.management.model.param.UpdateHotelAccountRechargeParam;
import com.jctrip.hotel.management.model.result.*;
import com.jctrip.hotel.management.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Service
@Slf4j
public class FinanceService {
    @Autowired
    private HtOrderRouteRecordDao htOrderRouteRecordDao;

    @Autowired
    private HtOrderSourceRecordDao htOrderSourceRecordDao;

    @Autowired
    private HtRouteInfoDao htRouteInfoDao;

    @Autowired
    private HtSourceInfoDao htSourceInfoDao;

    @Autowired
    private HtRouteCreditLogDao htRouteCreditLogDao;

    public GetFinanaceInfoResult getFinanaceInfo(GetFinanceInfoParam param) {
        Date beginDate;
        Date endDate;
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        if (StringUtils.isBlank(param.getDate())) {
            param.setDate(dateFormat.format(new Date()));
        }
        Long orderCount = 0L;
        Long roomNightCount = 0L;
        BigDecimal paymentPrice = BigDecimal.ZERO;
        BigDecimal purchasePrice = BigDecimal.ZERO;
        List<GetFinanaceInfoResult.RouteInfo> routeInfoList = new ArrayList<>();
        List<GetFinanaceInfoResult.SourceInfo> sourceInfoList = new ArrayList<>();
        try {
            beginDate = DateUtil.beginOfDay(dateFormat.parse(param.getDate()));
            endDate = DateUtil.endOfDay(dateFormat.parse(param.getDate()));
            List<HtOrderRouteRecord> htOrderRouteRecords = htOrderRouteRecordDao.selectList(new LambdaQueryWrapper<HtOrderRouteRecord>()
                    .between(HtOrderRouteRecord::getOrderCreateTime, beginDate, endDate)
                    .select(HtOrderRouteRecord::getOrderId, HtOrderRouteRecord::getArriveDate, HtOrderRouteRecord::getDepartDate
                            , HtOrderRouteRecord::getRoomCount, HtOrderRouteRecord::getPaymentPrice));

            for (HtOrderRouteRecord htOrderRouteRecord : htOrderRouteRecords) {
                paymentPrice = paymentPrice.add(htOrderRouteRecord.getPaymentPrice());
                HtOrderSourceRecord htOrderSourceRecord = htOrderSourceRecordDao.selectOne(new LambdaQueryWrapper<HtOrderSourceRecord>()
                        .eq(HtOrderSourceRecord::getOrderId, htOrderRouteRecord.getOrderId())
                        .orderByDesc(HtOrderSourceRecord::getCreateTime).last("limit 1")
                        .select(HtOrderSourceRecord::getPurchasePrice));
                if(htOrderSourceRecord != null && htOrderSourceRecord.getPurchasePrice() != null) {
                    purchasePrice = purchasePrice.add(htOrderSourceRecord.getPurchasePrice());
                }
                orderCount++;
                roomNightCount += DateUtil.between(htOrderRouteRecord.getArriveDate(),
                        htOrderRouteRecord.getDepartDate(), DateUnit.DAY) * htOrderRouteRecord.getRoomCount();
            }

            htOrderRouteRecords = htOrderRouteRecordDao.selectList(new QueryWrapper<HtOrderRouteRecord>()
                    .between("order_create_time", beginDate, endDate)
                    .groupBy("route_id")
                    .select("route_id,sum(payment_price) as payment_price"));
            AtomicReference<Long> id = new AtomicReference<>(1L);
            htOrderRouteRecords.forEach(htOrderRouteRecord -> {
                GetFinanaceInfoResult.RouteInfo routeInfo = GetFinanaceInfoResult.RouteInfo.builder()
                        .id(id.getAndSet(id.get() + 1))
                        .totalPaymentPrice(htOrderRouteRecord.getPaymentPrice())
                        .build();
                routeInfo.setRouteName(htRouteInfoDao.selectOne(new LambdaQueryWrapper<HtRouteInfo>()
                        .eq(HtRouteInfo::getRouteId,htOrderRouteRecord.getRouteId())
                        .select(HtRouteInfo::getRouteName)).getRouteName());
                routeInfoList.add(routeInfo);
            });

            id.set(1L);
            List<HtOrderSourceRecord> htOrderSourceRecords = htOrderSourceRecordDao.selectList(new QueryWrapper<HtOrderSourceRecord>()
                    .between("order_create_time", beginDate, endDate)
                    .groupBy("source_id")
                    .select("source_id,sum(purchase_price) as purchase_price"));
            htOrderSourceRecords.forEach(htOrderSourceRecord -> {
                GetFinanaceInfoResult.SourceInfo sourceInfo = GetFinanaceInfoResult.SourceInfo.builder()
                        .totalPurchasePrice(htOrderSourceRecord.getPurchasePrice())
                        .id(id.getAndSet(id.get() + 1)).build();
                sourceInfo.setSourceName(htSourceInfoDao.selectOne(new LambdaQueryWrapper<HtSourceInfo>()
                        .eq(HtSourceInfo::getSourceId,htOrderSourceRecord.getSourceId())
                        .select(HtSourceInfo::getSourceName)).getSourceName());
                sourceInfoList.add(sourceInfo);
            });
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }

        return GetFinanaceInfoResult.builder()
                .orderCount(orderCount)
                .paymentPrice(paymentPrice)
                .profitPrice(paymentPrice.subtract(purchasePrice))
                .purchasePrice(purchasePrice)
                .roomNightCount(roomNightCount)
                .routeInfoList(routeInfoList)
                .sourceInfoList(sourceInfoList).build();
    }

    public GetPurchaseResult purchase(String date, Long pageNo, Long pageSize) throws ParseException {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        if(StringUtils.isBlank(date)){
            date = dateFormat.format(new Date());
        }
        Date beginDate = DateUtil.beginOfDay(dateFormat.parse(date));
        Date endDate = DateUtil.endOfDay(dateFormat.parse(date));
        List<String> collect = htOrderSourceRecordDao.selectList(new LambdaQueryWrapper<HtOrderSourceRecord>()
                .between(HtOrderSourceRecord::getCreateTime, beginDate, endDate)
                .groupBy(HtOrderSourceRecord::getSourceId)
                .select(HtOrderSourceRecord::getSourceId)).stream().map(HtOrderSourceRecord::getSourceId).collect(Collectors.toList());
        Page<HtSourceInfo> sourceInfoPage = new Page<>();
        sourceInfoPage.setCurrent(pageNo);
        sourceInfoPage.setSize(pageSize);
        LambdaQueryWrapper<HtSourceInfo> sourceInfoLambdaQueryWrapper = new LambdaQueryWrapper<>();
        sourceInfoLambdaQueryWrapper.in(HtSourceInfo::getSourceId, collect).orderBy(true,true, HtSourceInfo::getSourceCode);
        Page<HtSourceInfo> htSourceInfoPage = htSourceInfoDao.selectPage(sourceInfoPage, sourceInfoLambdaQueryWrapper);
        Date beginMonth = DateUtil.beginOfMonth(beginDate);
        Date endMonth = DateUtil.endOfMonth(endDate);

        List<String> sourceCodeList = new ArrayList<>(htSourceInfoPage.getRecords().stream().map(HtSourceInfo::getSourceCode)
                .collect(Collectors.toSet()));
        List<GetPurchaseResult.PurchaseInfo> records = new ArrayList<>();
        sourceCodeList.forEach(sourceCode -> {
            GetPurchaseResult.PurchaseInfo purchaseInfo = GetPurchaseResult.PurchaseInfo.builder().sourceCode(sourceCode).build();
            List<HtSourceInfo> htSourceInfos = htSourceInfoPage.getRecords()
                    .stream().filter(item -> item.getSourceCode().equals(sourceCode)).collect(Collectors.toList());
            List<GetPurchaseResult.PurchaseDetailInfo> detailInfos = new ArrayList<>();
            htSourceInfos.forEach(htSourceInfo -> {
                GetPurchaseResult.PurchaseDetailInfo purchaseDetailInfo = GetPurchaseResult.PurchaseDetailInfo.builder()
                        .supplierName(htSourceInfo.getSourceName()).build();

                QueryWrapper<HtOrderSourceRecord> htOrderSourceRecordLambdaQueryWrapper = new QueryWrapper<>();
                htOrderSourceRecordLambdaQueryWrapper.between("source_order_status", 1,5)
                        .between("create_time", beginDate, endDate)
                        .eq("source_id", htSourceInfo.getSourceId())
                        .select("sum(room_count * datediff(depart_date, arrive_date)) as id,count(1) as room_count, sum(purchase_price) as purchase_price");
                HtOrderSourceRecord htOrderSourceRecord = htOrderSourceRecordDao.selectOne(htOrderSourceRecordLambdaQueryWrapper);

                purchaseDetailInfo.setDayAmount(htOrderSourceRecord.getPurchasePrice() != null ? htOrderSourceRecord.getPurchasePrice() : BigDecimal.ZERO);
                purchaseDetailInfo.setDayOrderCount((long) htOrderSourceRecord.getRoomCount());
                purchaseDetailInfo.setDayRoomNightQuantity(htOrderSourceRecord.getId() != null ? htOrderSourceRecord.getId() : 0L);

                htOrderSourceRecordLambdaQueryWrapper = new QueryWrapper<>();
                htOrderSourceRecordLambdaQueryWrapper.between("source_order_status", 1,5)
                        .between("create_time", beginMonth, endMonth)
                        .eq("source_id", htSourceInfo.getSourceId())
                        .select("sum(room_count * datediff(depart_date, arrive_date)) as id,count(1) as room_count, sum(purchase_price) as purchase_price");
                htOrderSourceRecord = htOrderSourceRecordDao.selectOne(htOrderSourceRecordLambdaQueryWrapper);

                purchaseDetailInfo.setMonthAmount(htOrderSourceRecord.getPurchasePrice() != null ? htOrderSourceRecord.getPurchasePrice() : BigDecimal.ZERO);
                purchaseDetailInfo.setMonthOrderCount((long) htOrderSourceRecord.getRoomCount());
                purchaseDetailInfo.setMonthRoomNightQuantity(htOrderSourceRecord.getId() != null ? htOrderSourceRecord.getId() : 0L);

                detailInfos.add(purchaseDetailInfo);
            });
            purchaseInfo.setDetails(detailInfos);
            records.add(purchaseInfo);
        });

        return GetPurchaseResult.builder()
                .current(htSourceInfoPage.getCurrent())
                .pages(htSourceInfoPage.getPages())
                .size(htSourceInfoPage.getSize())
                .total(htSourceInfoPage.getTotal())
                .records(records)
                .build();
    }

    public MarketResult market(String date, Long pageNo, Long pageSize) throws ParseException {
        Page<HtRouteInfo> routeInfoPage = new Page<>();
        routeInfoPage.setCurrent(pageNo);
        routeInfoPage.setSize(pageSize);
        LambdaQueryWrapper<HtRouteInfo> routeInfoLambdaQueryWrapper = new LambdaQueryWrapper<>();
        Page<HtRouteInfo> htRouteInfoPage = htRouteInfoDao.selectPage(routeInfoPage, routeInfoLambdaQueryWrapper);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        if(StringUtils.isBlank(date)){
            date = dateFormat.format(new Date());
        }
        Date beginDate = DateUtil.beginOfDay(dateFormat.parse(date));
        Date endDate = DateUtil.endOfDay(dateFormat.parse(date));
        List<MarketResult.MarkerInfo> markerInfoList = new ArrayList<>();
//        htRouteInfoPage.getRecords().forEach(htRouteInfo -> {
//            MarketResult.MarkerInfo markerInfo = MarketResult.MarkerInfo.builder()
//                    .sellerName(htRouteInfo.getRouteName()).build();
//            QueryWrapper<HtOrderRouteRecord> orderRouteRecordQueryWrapper = new QueryWrapper<>();
//            orderRouteRecordQueryWrapper.eq("route_id", htRouteInfo.getRouteId())
//                    .select("sum(room_count * (depart_date - arrive_date)) as id,count(1) as room_count,sum(payment_pric");
//
//            markerInfoList.add(markerInfo);
//        });

        return MarketResult.builder()
                .current(htRouteInfoPage.getCurrent())
                .pages(htRouteInfoPage.getPages())
                .size(htRouteInfoPage.getSize())
                .total(htRouteInfoPage.getTotal())
                .records(markerInfoList)
                .build();
    }

    public GetAccountResult hotelAccount(Long pageNo, Long pageSize, String routeCode, String routeName){
        Page<HtRouteInfo> page = new Page<>();
        page.setCurrent(pageNo);
        page.setSize(pageSize);
        LambdaQueryWrapper<HtRouteInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(StringUtils.isNotBlank(routeCode), HtRouteInfo::getRouteId, routeCode);
        lambdaQueryWrapper.like(StringUtils.isNotBlank(routeName), HtRouteInfo::getRouteName, routeName);
        Page<HtRouteInfo> htRouteInfoPage = htRouteInfoDao.selectPage(page, lambdaQueryWrapper);
        List<GetAccountResult.AccountInfo> records = new ArrayList<>();
        htRouteInfoPage.getRecords().forEach(htRouteInfo -> {
            GetAccountResult.AccountInfo accountInfo = GetAccountResult.AccountInfo.builder()
                    .balance(htRouteInfo.getAccountCredit())
                    .id(String.valueOf(htRouteInfo.getId()))
                    .sellerId(htRouteInfo.getRouteId())
                    .sellerName(htRouteInfo.getRouteName())
                    .sellerType(htRouteInfo.getRouteType())
                    .status(htRouteInfo.getRouteStatus())
                    .build();
            records.add(accountInfo);
        });
        return GetAccountResult.builder()
                .current(htRouteInfoPage.getCurrent())
                .pages(htRouteInfoPage.getPages())
                .size(htRouteInfoPage.getSize())
                .total(htRouteInfoPage.getTotal())
                .records(records)
                .build();
    }

    public String hotelAccountRecharge(UpdateHotelAccountRechargeParam param, HttpServletRequest request){
        if(StringUtils.isBlank(param.getId())){
            throw new RuntimeException("主键不能为空");
        }
        String token = request.getHeader("token");
        if(StringUtils.isBlank(token)){
            throw new RuntimeException("请先登录!");
        }
        String userName = JwtUtil.parseJwt(token).getSubject();
        if(null == param.getAmount()){
            throw new RuntimeException("充值金额不能为空");
        }
        HtRouteInfo htRouteInfo = htRouteInfoDao.selectOne
                (new LambdaQueryWrapper<HtRouteInfo>().eq(HtRouteInfo::getId, Long.valueOf(param.getId())));
        if(null == htRouteInfo){
            throw new RuntimeException("渠道账户不存在");
        }
        htRouteInfo.setAccountCredit(htRouteInfo.getAccountCredit().add(param.getAmount()));
        Boolean result = htRouteInfoDao.updateById(htRouteInfo) == 1;
        //增加日志
        insertRouteCreditLog(htRouteInfo, userName, param.getAmount(), param.getNotes());
        return result ? "充值成功!" : "充值失败!";
    }

    public String getUserAccount(String routeId) {
        BigDecimal accountCredit = Optional.ofNullable(htRouteInfoDao.selectOne(
                new LambdaQueryWrapper<HtRouteInfo>()
                        .eq(HtRouteInfo::getRouteId, routeId)))
                .orElse(new HtRouteInfo()).getAccountCredit();
        return Objects.nonNull(accountCredit) ? String.format("%.2f", accountCredit) : null;
    }

    public List<GetHotelAccountLogResult> hotelAccountLog(String routeId){
        List<HtRouteCreditLog> htRouteCreditLogs = htRouteCreditLogDao.selectList(new LambdaQueryWrapper<HtRouteCreditLog>()
                .eq(HtRouteCreditLog::getRouteId, routeId).orderByDesc(HtRouteCreditLog::getCreateTime));
        if(CollectionUtils.isEmpty(htRouteCreditLogs)){
            return null;
        }

        List<GetHotelAccountLogResult> logResults = new ArrayList<>();
        htRouteCreditLogs.forEach(htRouteCreditLog -> {
            GetHotelAccountLogResult getHotelAccountLogResult = GetHotelAccountLogResult.builder()
                    .bigDecimal(htRouteCreditLog.getAccountCredit())
                    .createTime(htRouteCreditLog.getCreateTime())
                    .operator(htRouteCreditLog.getOperator()).build();
            if(htRouteCreditLog.getCreditType() == 1){
                getHotelAccountLogResult.setInfo(String.format("商家%s订单下单,金额扣除" , htRouteCreditLog.getOrderId()));
            }
            if(htRouteCreditLog.getCreditType() == 2){
                getHotelAccountLogResult.setInfo(String.format("商家%s订单退款,金额退还" , htRouteCreditLog.getOrderId()));
            }
            if(htRouteCreditLog.getCreditType() == 3){
                getHotelAccountLogResult.setInfo("商家充值");
                if (StringUtils.isNotBlank(htRouteCreditLog.getNotes())) {
                    getHotelAccountLogResult.setInfo(String.format("%s,备注为%s",
                            getHotelAccountLogResult.getInfo(), htRouteCreditLog.getNotes()));
                }
            }
            logResults.add(getHotelAccountLogResult);
        });
        return logResults;
    }

    private void insertRouteCreditLog(HtRouteInfo htRouteInfo, String opeartor, BigDecimal amount, String notes) {
        if (htRouteInfo.getRouteType() != RouteTypeEnum.OUTER.getCode()) {
            return;
        }
        HtRouteCreditLog htRouteCreditLog = new HtRouteCreditLog();
        htRouteCreditLog.setRouteId(htRouteInfo.getRouteId());
        htRouteCreditLog.setOrderId("");
        htRouteCreditLog.setCreditType(3);
        htRouteCreditLog.setNotes(notes);
        htRouteCreditLog.setAmountOfChange(amount);
        htRouteCreditLog.setAccountCredit(htRouteInfo.getAccountCredit());
        htRouteCreditLog.setOperator(opeartor);
        htRouteCreditLogDao.insert(htRouteCreditLog);
    }
}
